﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Resources;
using System.Text;

namespace ResourceExtension
{
    public class ResourceXReader
    {
        public static ResourceMap Read(Stream resourceStream)
        {
            using (BinaryReader binaryReader = new BinaryReader(resourceStream, Encoding.UTF8))
            {
                return ReadStream(binaryReader);
            }
        }

        private static ResourceMap ReadStream(BinaryReader binaryReader)
        {
            ResourceMap resourceMap = new ResourceMap();
            
            try
            {
                foreach (KeyValuePair<string, object> pair in LoadStream(binaryReader))
                {
                    resourceMap.Add(pair.Key, pair.Value);
                }
            }
            catch (LoadResourceException)
            {
                return new ResourceMap();
            }
            catch(NotSupportedException)
            {
                return new ResourceMap();
            }
         
            return resourceMap;
        }

        private static IEnumerable<KeyValuePair<string,object>> LoadStream(BinaryReader stream)
        {
            if (!IsResource(stream))
                throw new LoadResourceException();
            
            int headerLength;
            if (NeedReadHeader(stream, out headerLength))
            {
                /*string str = */
                stream.ReadString();
                SkipString(stream);
            }
            else
            {
                stream.BaseStream.Seek(headerLength, SeekOrigin.Current);
            }

            int version = stream.ReadInt32();

            if ((version != 2) && (version != 1))
                throw new NotSupportedException();

            int resourcesLength = stream.ReadInt32();
            
            if (resourcesLength < 0)
                throw new LoadResourceException();

            int typesLength = stream.ReadInt32();

            if (typesLength < 0)
                throw new LoadResourceException();

            //Type[] typeTable = new Type[typesLength];
            int[] typeNamePositions = new int[typesLength];
            for (int i = 0; i < typesLength; i++)
            {
                typeNamePositions[i] = (int)stream.BaseStream.Position;
                SkipString(stream);
            }

            int resourceShift = ((int)stream.BaseStream.Position) & 7;
            if (resourceShift != 0)
                for (int j = 0; j < (8 - resourceShift); j++)
                    stream.ReadByte();

            ReadHashes(stream, resourcesLength);

            int[] namePositions = ReadPosition(stream, resourcesLength);

            int dataSectionOffset = stream.ReadInt32();
            if (dataSectionOffset < 0)
                throw new LoadResourceException();
            
            long nameSectionOffset = stream.BaseStream.Position;
            if (dataSectionOffset < nameSectionOffset)
            {
                throw new LoadResourceException();
            }

            for (int index = 0; index < resourcesLength; index++)
            {
                string key = GetName(stream, nameSectionOffset, namePositions[index],
                                                        dataSectionOffset);

                int position = GetPositionOfValue(stream, nameSectionOffset, namePositions[index],
                                                        dataSectionOffset);

                object value = GetValue(stream, dataSectionOffset, position, version);

                yield return new KeyValuePair<string, object>(key, value);
            }


           /* // remove
            context._numResources = resourcesLength;

            return context;*/
        }

        private static int[] ReadPosition(BinaryReader stream, int resourcesLength)
        {
            int[] namePositions = new int[resourcesLength];
            for (int i = 0; i < resourcesLength; i++)
            {
                int position = stream.ReadInt32();
                namePositions[i] = position;
            }
            return namePositions;
        }

        private static int[] ReadHashes(BinaryReader stream, int resourcesLength)
        {
            int[] hashes = new int[resourcesLength];
            for (int i = 0; i < resourcesLength; i++)
                hashes[i] = stream.ReadInt32();
            return hashes;
        }

        private static bool NeedReadHeader(BinaryReader stream, out int headerLength)
        {
            int statusOfHeader = stream.ReadInt32();
            headerLength = stream.ReadInt32();
            if ((headerLength < 0) || (statusOfHeader < 0))
                throw new LoadResourceException();
            return !(statusOfHeader > 1);
        }

        private static bool IsResource(BinaryReader stream)
        {
            return (stream.ReadInt32() == ResourceManager.MagicNumber);
        }

        private static void SkipString(BinaryReader _store)
        {
            int num = Read7BitEncodedInt(_store);
            if (num < 0)
            {
                throw new LoadResourceException();
            }
            _store.BaseStream.Seek(num, SeekOrigin.Current);
        }

        private static int Read7BitEncodedInt(BinaryReader reader)
        {
            byte num3;
            int num = 0;
            int num2 = 0;
            do
            {
                if (num2 == 0x23)
                {
                    throw new LoadResourceException();
                }
                num3 = reader.ReadByte();
                num |= (num3 & 0x7f) << num2;
                num2 += 7;
            }
            while ((num3 & 0x80) != 0);
            return num;

        }

        private static string GetName(BinaryReader stream, long nameSectionOffset, long namePosition, long dataSectionOffset)
        {
            byte[] buffer;
            int num;
            int num4;
            stream.BaseStream.Seek(namePosition + nameSectionOffset, SeekOrigin.Begin);
            num = Read7BitEncodedInt(stream);
            if (num < 0)
            {
                throw new LoadResourceException();
            }

            buffer = new byte[num];
            for (int i = num; i > 0; i -= num4)
            {
                num4 = stream.Read(buffer, num - i, i);
                if (num4 == 0)
                {
                    throw new LoadResourceException();
                }
            }
            int dataOffset = stream.ReadInt32();
            if ((dataOffset < 0) || ((dataOffset) >= (stream.BaseStream.Length - dataSectionOffset)))
            {
                throw new LoadResourceException();
            }

            return Encoding.Unicode.GetString(buffer, 0, num);
        }

        private static int GetPositionOfValue(BinaryReader stream, long nameSectionOffset, long namePosition, long dataSectionOffset)
        {
            //ResourceTypeCode code;
            stream.BaseStream.Seek(namePosition + nameSectionOffset, SeekOrigin.Begin);
            SkipString(stream);
            int pos = stream.ReadInt32();
            if ((pos < 0) || (pos >= (stream.BaseStream.Length - dataSectionOffset)))
            {
                throw new Exception();
            }

            return pos;
        }

        private static object GetValue(BinaryReader stream, long dataSectionOffset, int postion, int version)
        {
            int typeIndex;
            if (version == 1)
            {
                /*stream.BaseStream.Seek(dataSectionOffset + postion,
                    SeekOrigin.Begin);

                typeIndex = Read7BitEncodedInt(stream);

                return stream.ReadString();*/

                throw new NotSupportedException();
            }

            stream.BaseStream.Seek(dataSectionOffset + postion,
                    SeekOrigin.Begin);

            typeIndex = Read7BitEncodedInt(stream);

            if (typeIndex != (int)ResourceTypeCode.String)
                throw new NotSupportedException();

            return stream.ReadString();
        }

        private enum ResourceTypeCode
        {
            Boolean = 2,
            Byte = 4,
            ByteArray = 0x20,
            Char = 3,
            DateTime = 15,
            Decimal = 14,
            Double = 13,
            Int16 = 6,
            Int32 = 8,
            Int64 = 10,
            LastPrimitive = 0x10,
            Null = 0,
            SByte = 5,
            Single = 12,
            StartOfUserTypes = 0x40,
            Stream = 0x21,
            String = 1,
            TimeSpan = 0x10,
            UInt16 = 7,
            UInt32 = 9,
            UInt64 = 11
        }

        class LoadResourceException : Exception
        {

        }

    }
}
